【elasticsearch】如何部分更新文档 (partial update的使用) |
您所在的位置:网站首页 › es 覆盖更新 › 【elasticsearch】如何部分更新文档 (partial update的使用) |
文章目录
1.什么是partial update1.1 全量修改文档的原理1.2 修改指定field的思路1.3 partial update的优势1.4 partial update的使用
2 通过脚本进行partial update操作2.1 内置painless脚本修改文档2.2 外置Groovy脚本修改文档2.3 内置painless脚本upsert文档2.4 外置Groovy脚本delete文档
3 partial update的并发控制策略3.1 控制方式3.2 retry原理
![]() 全量修改文档的语法: PUT index/type/1, 如果id=1的文档不存在, 则创建, 如果存在, 将发生替换原有文档的操作. 全量替换文档的性能比较低, 为了避免替换操作的发生, 引入partial update: 只修改指定的field, 不用全量修改数据. 1.2 修改指定field的思路(1) 根据用户请求, 获得要修改的文档; (2) 在内存中封装用户提交的新文档, 发送PUT请求到ES内部; (3) 将要替换的旧文档标记为deleted; (4) 最后将封装好的新文档存入索引中. 1.3 partial update的优势(1) 所有的查询、修改和写回操作, 都在同一个shard中进行, 避免了网络传输的开销. 不需要: 从特定shard查询文档 -> 返回到内存 -> 内存中修改 -> 将修改的文档发送到原来的shard -> 写索引 —— 这个复杂的操作, 显著提升了性能. (2) 减少了查询和修改的时间间隔, 可以有效减少并发冲突. 1.4 partial update的使用使用方法: 通过_update关键字实现增量更新: // 添加测试数据: PUT employee/developer/1 { "name": "shou feng", "sex": "male", "age": 20 } // partial update修改指定field: POST employee/developer/1/_update { "doc": { "age": 21 } } // 响应结果: { "_index": "employee", "_type": "developer", "_id": "1", "_version": 2, "result": "updated", "_shards": { "total": 2, "successful": 1, "failed": 0 } } // 查看文档, 发现age已经从20变为21了. GET employee/developer/1如果不使用_update, 则会直接覆盖掉源文档, 导致原文档丢失部分数据: // 不使用_update: POST employee/developer/1 { "doc": { "age": 22 } } // 再次查看, 发现id=1的该文档就只剩一个age字段了: GET employee/developer/1 2 通过脚本进行partial update操作ES提供了脚本支持 —— 可以通过Groovy外置脚本(已过时)、内置painless脚本实现各种复杂操作. 2.1 内置painless脚本修改文档插入文档: PUT employee/developer/1 { "name": "shou feng", "age": 20, "salary": 10000 }执行脚本: —— 这里使用的是更轻快简短的painless脚本, 就是直接由字符串表示的脚本: POST employee/developer/1/_update // 发送POST请求, 执行partial update { "script": "ctx._source.salary+=500" // 为salary自增500 }查看修改结果: GET employee/developer/1 // 结果如下: { "_index": "employee", "_type": "developer", "_id": "1", "_version": 5, "found": true, "_source": { "name": "shou feng", "age": 20, "salary": 10500 // 自增500成功 } } 2.2 外置Groovy脚本修改文档说明: 在ES 6.x版本之后, groovy脚本不再支持, 这里演示所用的是ES 5.6.10版本, 如果在6.x版本中使用, 将会抛出如下异常: "type": "illegal_argument_exception", "reason": "script_lang not supported [groovy]"将脚本文件存放在${ES_HOME}/config/scripts下, 文件名为xxx.groovy, 内容为: ctx._source.salary+=bonus —— 增加值为将近bonus的值, 脚本信息示例如下: [root@localhost scripts]# pwd /data/elk-5.6.10/es-node/config/scripts [root@localhost scripts]# cat change_salary.groovy ctx._source.salary+=bonus [root@localhost scripts]#修改文档: POST employee/developer/1/_update { "script": { "lang": "groovy", "file": "change_salary", "params": { "bonus": 500 } } } // 响应结果为: #! Deprecation: [groovy] scripts are deprecated, use [painless] scripts instead { "_index": "employee", "_type": "developer", "_id": "1", "_version": 6, "result": "updated", "_shards": { "total": 2, "successful": 1, "failed": 0 } }查看修改结果: GET employee/developer/1 // 结果如下: { "_index": "employee", "_type": "developer", "_id": "1", "_version": 6, "found": true, "_source": { "name": "shou feng", "age": 20, "salary": 9000 } }说明: 在执行外置Groovy脚本时, ES提示Groovy脚本已经过时, 建议我们使用painless —— 更轻快的表达方式, 即类似于ctx._source.salary+=bonus的简短表达方式. Elasticsearch 5.6开始, 默认脚本使用的方式就已经是painless了. 关于脚本的详细使用, 请查看博文: ES 27 - Elasticsearch的painless脚本使用实践. 2.3 内置painless脚本upsert文档(先删除id=1的文档: DELETE employee/developer/1) 假设我们并不知道id=1的文档已经被删除了, 现在为其添加"level": 1的内容: POST employee/developer/1/_update { "doc": { "level": 1 } }抛出 [404 - 文档丢失] 的错误: { "error": { "root_cause": [ { "type": "document_missing_exception", "reason": "[developer][1]: document missing", "index_uuid": "rT6tChP2QISaVd2OzdCEMA", "shard": "3", "index": "employee" } ], "type": "document_missing_exception", "reason": "[developer][1]: document missing", "index_uuid": "rT6tChP2QISaVd2OzdCEMA", "shard": "3", "index": "employee" }, "status": 404 }修改upsert策略: 如果指定的文档不存在, 就执行upsert中的初始化操作; 如果存在, 就执行doc或script中的partial update操作: POST employee/developer/1/_update { "script": "ctx.source.level+=1", "upsert": { "name": "heal", "age": 20 } }此时发现"result" : “created” —— 新建了文档. 2.4 外置Groovy脚本delete文档说明: 这里演示所用的是ES 5.6.10版本. 脚本路径: ${ES_HOME}/config/scripts/delete_doc.groovy 脚本内容: ctx.op = ctx._source.age == age ? 'delete': 'none' ctx.op = ctx._source.age == param ? 'delete' : 'none' 使用示例: POST employee/developer/1/_update { "script": { "lang": "groovy", "file": "delete_doc", "params": { "age": 20 // 如果年龄是20, 则删除之 } } }响应结果: #! Deprecation: [groovy] scripts are deprecated, use [painless] scripts instead { "_index": "employee", "_type": "developer", "_id": "1", "_version": 13, "result": "deleted", "_shards": { "total": 2, "successful": 1, "failed": 0 } }查看文档是否被删除: GET employee/developer/1 // 响应结果 - 成功删除: { "_index": "employee", "_type": "developer", "_id": "1", "found": false } 3 partial update的并发控制策略partial update内部也是通过乐观锁进行并发控制的. 关于并发控制, 请参见博文: Elasticsearch的并发控制策略. 3.1 控制方式 POST index/type/id/_update?retry_on_conflict=5 POST index/type/id/_update?retry_on_conflict=5&version=5 3.2 retry原理retry_on_conflict: 发生冲突后的重试次数. (1) 客户端A、B几乎同时获取同一个文档, 一并获得_version版本信息, 假设此时_version=1; (2) 客户端A修改文档中的部分内容, 将修改写入索引; (3) Elasticsearch在写入索引时, 检查客户端A提交的文档的版本信息(这里仍然是1) 和 现存的文档的版本信息(这里也是1), 发现相同后, 执行写入操作, 并修改版本号_version=2; (4) 客户端B也修改文档中的部分内容, 其操作写回索引的速度稍慢. 此时同样执行过程(3): ES发现客户端B提交的文档的版本为1, 而现存文档的版本为2 ===> 发生冲突, 此次partial update将失败; (5) partial update操作失败后, 将重复(1) - (3) 过程, 重复的次数, 就是retry_on_conflict参数的值. |
今日新闻 |
推荐新闻 |
CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3 |